`include "define.svh"

module cp0(
    input wire                              clk,
    input wire                              rstn,
    input wire [5 : 0]                      ext_int,
    
    input wire [`REG_ADDR_WIDTH - 1 : 0]    mfc_raddr,
    output reg [`REG_WIDTH - 1 : 0]         mfc_rdata,
    
    input wire                              mtc_wen,
    input wire [`REG_ADDR_WIDTH - 1 : 0]    mtc_waddr,
    input wire [`REG_WIDTH - 1 : 0]         mtc_wdata,
    
    output reg                              status_exl_o,
    output reg                              status_ie_o,
    output reg [7 : 0]                      status_im_o,
    output reg [5 : 0]                      cause_ip_7_2_o,
    output reg [1 : 0]                      cause_ip_1_0_o,
    output reg [31 : 0]                     epc_o,
    
    input wire                              is_exc,
    input wire                              is_eret,
    input wire                              is_Adexc,
    
    input wire [31 : 0]                     epc_wdata,
    input wire                              cause_bd_wdata,
    input wire                              status_exl_wdata,
    input wire [31 : 0]                     badvaddr_wdata,
    input wire [4 : 0]                      cause_excode_wdata
);
    // BadVAddr register, CP0, Register 8, Select 0
    reg [31 : 0]    badvaddr;       // read only
    // Count register, CP0, Register 9, Select 0
    reg [31 : 0]    count;          // read only
    // Status register, CP0, Rediter 12, Select 0
    reg             status_bev;     // read only
    reg [7 : 0]     status_im;      // read & write
    reg             status_exl;     // read & write
    reg             status_ie;      // read & write
    // Cause register, CP0, Register 13, Select 0
    reg             cause_bd;       // read & refresh only
    reg             cause_ti;       // read & refresh only
    reg [5 : 0]     cause_ip_7_2;   // read & refresh only
    reg [1 : 0]     cause_ip_1_0;   // read & write
    reg [4 : 0]     cause_excode;   // read & refresh only
    // EPC register, CP0, Register 14, Select 0
    reg [31 : 0]    epc;            // read & write
    
    wire [6 : 0] cause_ip = {cause_ip_7_2, cause_ip_1_0};
    
    /* ************* MFC0 Instr *************** */
    always_comb begin
        case(mfc_raddr) 
            5'd8:       
                mfc_rdata = badvaddr;
            5'd9:       
                mfc_rdata = count;
            5'd12:      
                mfc_rdata = {9'o000, status_bev, 6'o00, status_im, 6'o00, status_exl, status_ie};
            5'd13:      
                mfc_rdata = {cause_bd, cause_ti, 15'o00000, cause_ip, 1'b0, cause_excode, 2'b00};
            5'd14:
                mfc_rdata = epc;
            default:
                mfc_rdata = 32'h0000_0000;
        endcase
    end
    
    /* *************** Register ******************* */
    
    // BadVAddr reg
    always_ff @(posedge clk) begin
        if (is_Adexc)           badvaddr <= badvaddr_wdata;
    end
    
    // Count reg
    wire count_write = mtc_wen && mtc_waddr == 5'd9;
    reg tick;
    always_ff @(posedge clk) begin
        if (rstn == `reset)     tick <= 1'b0;
        else                    tick <= ~tick;
    end
    always_ff @(posedge clk) begin
        if (rstn == `reset)        count <= 32'h0000_0000;
        else if (count_write)      count <= mtc_wdata;
        else if (tick)             count <= $unsigned(count) + $unsigned(1);
    end
    
    // Status reg
    wire status_write = mtc_wen && mtc_waddr == 5'd12;
    always_comb begin
        status_bev = 1'b1;
    end
    always_ff @(posedge clk) begin
        if (rstn == `reset)        status_im <= 7'b1111111;
        else if (status_write)     status_im <= mtc_wdata[15 : 8];
    end
    always_ff @(posedge clk) begin
        if (rstn == `reset)             status_exl <= 1'b0;
        else if ((is_exc | is_eret))    status_exl <= status_exl_wdata;
        else if (status_write)          status_exl <= mtc_wdata[1];
    end
    always_ff @(posedge clk) begin
        if (rstn == `reset)        status_ie <= 1'b0;
        else if (status_write)     status_ie <= mtc_wdata[0];
    end
    
    // Cause reg
    wire cause_write = mtc_wen && mtc_waddr == 5'd13;
    always_comb cause_ti = 1'b0;
    always_ff @(posedge clk) begin
        if (rstn == `reset)         cause_ip_7_2 <= 6'b000000;
        else                        cause_ip_7_2 <= ext_int;
    end
    always_ff @(posedge clk) begin
        if (rstn == `reset)         cause_ip_1_0 <= 2'b00;
        else if (cause_write)       cause_ip_1_0 <= mtc_wdata[9 : 8];
    end
    always_ff @(posedge clk) begin
        if (is_exc)                 cause_bd <= cause_bd_wdata;
    end
    always_ff @(posedge clk) begin
        if (is_exc)                 cause_excode <= cause_excode_wdata;
    end
    
    // EPC reg
    wire epc_write = mtc_wen && mtc_waddr == 5'd14;
    always_ff @(posedge clk) begin
        if (epc_write)              epc <= mtc_wdata;
        else if (is_exc)            epc <= epc_wdata;
    end
    
    /* ********************* Read Output ************************ */
    always_comb status_exl_o = status_exl;
    always_comb status_ie_o = status_ie;
    always_comb status_im_o = status_im;
    always_comb cause_ip_7_2_o = cause_ip_7_2;
    always_comb cause_ip_1_0_o = cause_ip_1_0;
    always_comb epc_o = epc;
    
endmodule
